Hook

Hook은 React 버전 16.8부터 React 요소로 새로 추가되었다.

Hook은 함수 컴포넌트에서 React state와 생명주기 기능(lifecycle features)을 “연동(hook into)“할 수 있게 해주는 함수이다.

React에서 제공하는 내장 Hook

  • useState
  • useEffect
  • useContext
  • (useReducer) ...

Hook을 사용하는 이유

Hook을 사용하면 컴포넌트로부터 상태 관련 로직을 추상화할 수 있음

클래스형 컴포넌트에서의 생명 주기

class FriendStatusWithCounter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0, isOnline: null };
    this.handleStatusChange = this.handleStatusChange.bind(this);
  }

  componentDidMount() {
    document.title = `You clicked ${this.state.count} times`;
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  componentDidUpdate() {
    document.title = `You clicked ${this.state.count} times`;
  }

  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  handleStatusChange(status) {
    this.setState({
      isOnline: status.isOnline
    });
  }
  // ...

document.title을 설정하는 로직이 componentDidMount와 componentDidUpdate에 나누어져 있다.

구독(subscription)로직 또한 componentDidMount와 componentWillUnmount에 나누어져 있다.

즉, componentDidMount가 두 가지의 작업을 위한 코드를 모두 가지고 있다...

함수형 컴포넌트에서 useEffect 활용

Effect를 이용하여 서로 관련이 없는 로직들을 갈라놓을 수 있다.

function FriendStatusWithCounter(props) {
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });
  // ...
}

Custom Hook 만들기

개발을 하다 보면 가끔 상태 관련 로직을 컴포넌트 간에 재사용하고 싶은 경우가 생긴다.

이 문제를 해결하기 위한 전통적인 방법이 두 가지

  • higher-order components
  • render props

Custom Hook은 이들 둘과는 달리 컴포넌트 트리에 새 컴포넌트를 추가하지 않고도 이것을 가능하게 해준다.

use{Name}와 같은 형태의 컨벤션을 따름

Custom Hook 예시

import React, { useState, useEffect } from "react";

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });

  return isOnline;
}

Custom Hook 적용 예

function FriendStatus(props) {
  const isOnline = useFriendStatus(props.friend.id);

  if (isOnline === null) {
    return "Loading...";
  }
  return isOnline ? "Online" : "Offline";
}

React의 Side Effect

useState / useEffect를 반복하는 경우 custom hook을 고려하자

<!DOCTYPE html>
<html lang="en">
  <body>
    <div id="root"></div>
    <script
      crossorigin
      src="https://unpkg.com/react@18/umd/react.development.js"
    ></script>
    <script
      crossorigin
      src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"
    ></script>
    <script
      crossorigin
      src="https://unpkg.com/@babel/standalone/babel.min.js"
    ></script>

    <script type="text/babel">
      const rootElement = document.getElementById("root");

      // custom hook
      function useLocalStorage(itemName, value = "") {
        const [state, setState] = React.useState(
            () => {
                return window.localStorage.getItem(itemName) || ""
            }
        );

         React.useEffect(() => {
            window.localStorage.setItem(itemName, state);
        }. [state])
      }

      const App = () => {
        const [keyword, setKeyword] = useLocalStorage("keyword")
        const [result, setResult] = useLocalStorage("result")
        const [typing, setTyping] = useLocalStorage("typing", false)

        function handleChange(event) {
            window.localStorage.setItem("keyword", event.target.value)
            setKeyword(event.target.value)
            setTyping(true);
        }

        function handleClick() {
            setTypeing(false)
            setResult(`We find result of ${keyword}`);
        }

        return (
            <>
                <input onChange={handleChange} value={keyword}> //
                <button onclick={handleClick}>search</button>
                <p>{typing ? `Looking for ${keyword}...`}</p>
            </>
        )

        ReactDOM.render(<App />, rootElement);
      }
    </script>
  </body>
</html>